package top.suven.base.core.mybatis.crud;



import org.apache.commons.lang3.StringUtils;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class CommonCrudProvider {

    public String createEntity(Map<String, Object> args) {

        Object entity = args.get("entity");

        String tableName = entity.getClass().getAnnotation(Table.class).name();
        StringBuilder columns = new StringBuilder();
        StringBuilder values = new StringBuilder();

        Field[] declaredFields = entity.getClass().getDeclaredFields();

        for (Field f : declaredFields) {
            if(!f.isAnnotationPresent(Column.class))
                continue;

            Column column = f.getAnnotation(Column.class);
            if(f.isAnnotationPresent(SoftDelete.class)) {
                columns.append(String.format(",%s", column.name()));
                values.append(String.format(",'%s'", f.getAnnotation(SoftDelete.class).normal()));

            } else if(f.isAnnotationPresent(GeneratedValue.class)) {
                GeneratedValue generatedValue = f.getAnnotation(GeneratedValue.class);
                if(generatedValue.strategy()== GenerationType.SEQUENCE) {
                    columns.append(String.format(",nextval('%s')", generatedValue.generator()));
                }else {
                    columns.append(String.format(",%s", column.name()));
                    values.append(String.format(",#{entity.%s}",f.getName()));
                }
            }else if(column.nullable() || getObject(entity, f) != null) {
                columns.append(String.format(",%s", column.name()));
                values.append(String.format(",#{entity.%s}",f.getName()));
            }
        }

        String sql = String.format("insert into %s(%s) values(%s)", tableName, columns.substring(1), values.substring(1));

        return sql;
    }

    public String batchCreateEntity(Map<String, Object> args) {

        List entityList = (List)args.get("entityList");
        Object entity = entityList.get(0);

        String tableName = entity.getClass().getAnnotation(Table.class).name();
        StringBuilder columns = new StringBuilder();
        StringBuilder values = new StringBuilder();

        Field[] declaredFields = entity.getClass().getDeclaredFields();

        for (Field f : declaredFields) {
            if(!f.isAnnotationPresent(Column.class))
                continue;

            Column column = f.getAnnotation(Column.class);
            if(f.isAnnotationPresent(SoftDelete.class)) {
                columns.append(String.format(",%s", column.name()));
                values.append(String.format(",'%s'", f.getAnnotation(SoftDelete.class).normal()));

            } else if(column.nullable() || getObject(entity, f) != null) {
                columns.append(String.format(",%s", column.name()));
                values.append(String.format(",#{entityList[%%1$d].%s}",f.getName()));
            }
        }

        StringBuilder sb = new StringBuilder();
        String v = values.substring(1);
        for(int i= 0; i < entityList.size(); i++) {
            sb.append(String.format(",(%s)", String.format(v, i)));
        }

        String sql = String.format("insert into %s(%s) values %s", tableName, columns.substring(1), sb.substring(1));

        return sql;
    }

    public String retrieveEntity(Map<String, Object> args) {

        Object entity = args.get("entity");

        String tableName = entity.getClass().getAnnotation(Table.class).name();
        StringBuilder select = new StringBuilder();
        StringBuilder where = new StringBuilder();

        Field[] declaredFields = entity.getClass().getDeclaredFields();

        for (Field f : declaredFields) {
            if(!f.isAnnotationPresent(Column.class))
                continue;

            Object paramValue = getObject(entity, f);
            if(paramValue != null && StringUtils.isNotBlank(paramValue.toString()))
                where.append(String.format(" and %s=#{entity.%s} ", f.getAnnotation(Column.class).name(), f.getName()));

            else if(f.isAnnotationPresent(SoftDelete.class)) {
                String normal = f.getAnnotation(SoftDelete.class).normal();
                where.append(String.format(" and %s='%s' ", f.getAnnotation(Column.class).name(), normal));
            }

            select.append(String.format(",%s as %s", f.getAnnotation(Column.class).name(), f.getName()));
        }

        String sql = String.format("select %s from %s where 1=1 %s limit 1 offset 0 ", select.substring(1), tableName, where);

        return sql;
    }

    public String buildFindEntitySql(Map<String, Object> args, boolean includeDeleted) {
        Object entity = args.get("entity");

        String tableName = entity.getClass().getAnnotation(Table.class).name();
        StringBuilder select = new StringBuilder();
        StringBuilder where = new StringBuilder();

        Field[] declaredFields = entity.getClass().getDeclaredFields();

        for (Field f : declaredFields) {
            if(!f.isAnnotationPresent(Column.class))
                continue;

            Object paramValue = getObject(entity, f);
            if(paramValue != null && StringUtils.isNotBlank(paramValue.toString()))
                where.append(String.format(" and %s=#{entity.%s} ", f.getAnnotation(Column.class).name(), f.getName()));

            else if(!includeDeleted && f.isAnnotationPresent(SoftDelete.class)) {
                String normal = f.getAnnotation(SoftDelete.class).normal();
                where.append(String.format(" and %s='%s' ", f.getAnnotation(Column.class).name(), normal));
            }

            select.append(String.format(",%s as %s", f.getAnnotation(Column.class).name(), f.getName()));
        }

        String sql = String.format("select %s from %s where 1=1 %s ", select.substring(1), tableName, where);

        return sql;
    }

    public String buildCountEntitySql(Map<String, Object> args, boolean includeDeleted) {
        Object entity = args.get("entity");

        String tableName = entity.getClass().getAnnotation(Table.class).name();
        StringBuilder where = new StringBuilder();

        Field[] declaredFields = entity.getClass().getDeclaredFields();

        for (Field f : declaredFields) {
            if(!f.isAnnotationPresent(Column.class))
                continue;

            Object paramValue = getObject(entity, f);
            if(paramValue != null && StringUtils.isNotBlank(paramValue.toString()))
                where.append(String.format(" and %s=#{entity.%s} ", f.getAnnotation(Column.class).name(), f.getName()));

            else if(!includeDeleted && f.isAnnotationPresent(SoftDelete.class)) {
                String normal = f.getAnnotation(SoftDelete.class).normal();
                where.append(String.format(" and %s='%s' ", f.getAnnotation(Column.class).name(), normal));
            }

        }

        String sql = String.format("select count(*) from %s where 1=1 %s ", tableName, where);

        return sql;
    }

    public String findPageEntity(Map<String, Object> args) {
        int start = (Integer) args.get("start");
        int pageSize = (Integer)args.get("pageSize");
        String sql = buildFindEntitySql(args, false);
        return String.format("%s limit %d offset (%d-1)*%d",sql,pageSize,start,pageSize);
    }

    public String findEntity(Map<String, Object> args) {
        return buildFindEntitySql(args, false);
    }

    public String countEntity(Map<String, Object> args) {
        return buildCountEntitySql(args, false);
    }

    public String findEntityIncludeDeleted(Map<String, Object> args) {
        return buildFindEntitySql(args, true);
    }

    public String findEntityByCondition(Map<String, Object> args) {
        String condition = String.valueOf(args.get("condition"));
        return String.format("%s %s", buildFindEntitySql(args, false), condition);
    }

    public String countEntityByCondition(Map<String, Object> args) {
        String condition = String.valueOf(args.get("condition"));
        return String.format("%s %s", buildCountEntitySql(args, false), condition);
    }

    public String findEntityIncludeDeletedByCondition(Map<String, Object> args) {
        String condition = String.valueOf(args.get("condition"));
        return String.format("%s %s", buildFindEntitySql(args, true), condition);
    }

    public String updateEntity(Map<String, Object> args) {

        Object entity = args.get("entity");

        String tableName = entity.getClass().getAnnotation(Table.class).name();
        StringBuilder setValues = new StringBuilder();
        StringBuilder where = new StringBuilder();

        boolean isSaveUpdate = false;

        Field[] declaredFields = entity.getClass().getDeclaredFields();
        for (Field f : declaredFields) {
            if(!f.isAnnotationPresent(Column.class))
                continue;

            if(f.isAnnotationPresent(GeneratedValue.class) && getObject(entity, f) != null) {
                isSaveUpdate = true;
                where.append(String.format("%s=#{entity.%s}", f.getAnnotation(Column.class).name(), f.getName()));
                continue;
            }

            Object paramValue = getObject(entity, f);
            if(paramValue != null && StringUtils.isNotBlank(paramValue.toString()))
                setValues.append(String.format(",%s=#{entity.%s}", f.getAnnotation(Column.class).name(), f.getName()));
        }

        if(!isSaveUpdate)
            throw new RuntimeException("Can not execute update statement without id condition");

        String sql = String.format("update %s set %s where %s", tableName, setValues.substring(1), where);

        return sql;
    }

    public String deleteEntity(Map<String, Object> args) {

        Object entity = args.get("entity");

        String tableName = entity.getClass().getAnnotation(Table.class).name();
        StringBuilder where = new StringBuilder();

        Field[] declaredFields = entity.getClass().getDeclaredFields();

        boolean isSoftDelete = false;
        boolean isSaveDelete = false;
        String softDelColName = "";
        String invalid = "";

        for (Field f : declaredFields) {
            if(!f.isAnnotationPresent(Column.class))
                continue;

            if(f.isAnnotationPresent(SoftDelete.class)) {
                isSoftDelete = true;
                softDelColName = f.getAnnotation(Column.class).name();
                invalid = f.getAnnotation(SoftDelete.class).invalid();
            }

            if(f.isAnnotationPresent(GeneratedValue.class) && getObject(entity, f) != null) {
                isSaveDelete = true;
                where.append(String.format("%s=#{entity.%s}", f.getAnnotation(Column.class).name(), f.getName()));
            }
        }

        if(!isSaveDelete)
            throw new RuntimeException("Can not execute delete statement without id condition");

        if(isSoftDelete) {
            return String.format("update %s set %s='%s' where %s", tableName, softDelColName, invalid, where);

        } else {
            return String.format("delete from %s where %s", tableName, where.substring(3));
        }
    }

    public String deleteEntityByCondition(Map<String, Object> args) {
        Object entity = args.get("entity");
        String condition = String.valueOf(args.get("condition"));
        String tableName = entity.getClass().getAnnotation(Table.class).name();

        Field[] declaredFields = entity.getClass().getDeclaredFields();
        Optional<Field> first = Arrays.stream(declaredFields).filter(f -> f.isAnnotationPresent(SoftDelete.class)).findFirst();
        if (first.isPresent()) {
            String softDelColName = first.get().getAnnotation(Column.class).name();
            String invalid = first.get().getAnnotation(SoftDelete.class).invalid();
            return String.format("update %s set %s='%s' where %s", tableName, softDelColName, invalid, condition);

        } else {
            return String.format("delete from %s where %s", tableName, condition);
        }
    }

    private Object getObject(Object entity, Field field) {

        Object obj = null;

        try {

            Class clazz = entity.getClass();
            PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
            Method getMethod = pd.getReadMethod();
            obj = getMethod.invoke(entity);

        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IntrospectionException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return obj;
    }

    private void setObject(Object entity, Field field, Object obj) {

        try {

            Class clazz = entity.getClass();
            PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
            Method setMethod = pd.getWriteMethod();
            setMethod.invoke(entity, obj);

        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IntrospectionException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

}
